💡 AI 인사이트

🤖 AI가 여기에 결과를 출력합니다...

댓글 커뮤니티

쿠팡이벤트

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

검색

    로딩 중이에요... 🐣

    [코담] 웹개발·실전 프로젝트·AI까지, 파이썬·장고의 모든것을 담아낸 강의와 개발 노트

    32 1 React + Django(DRF+Djoser) JWT 인증 시스템 구현 | ✅ 편저: 코담 운영자

    - Djoser를 활용한 인증 시스템 구축 & JWT 베스트 프랙티스


    📌 본 문서는 React(Vite)와 Django Rest Framework(Djoser)를 활용해 로그인/로그아웃 기능을 포함한

    JWT 인증 시스템을 구축하는 전체 과정을 설명합니다.

    📁 프로젝트 구조

    
    Chapter32_djoser/
    ├── backend/       ← Django 백엔드 (Djoser 포함)
    └── frontend/      ← React 프론트엔드 (Vite 기반)
    
    

    1️⃣ 백엔드 설정 (Django + DRF + Djoser)

    1-1. Djoser 및 SimpleJWT 설치

     pip install djoser djangorestframework djangorestframework-simplejwt
    
    

    1-2. settings.py 설정

    INSTALLED_APPS = [
        ...
        'rest_framework',
        'rest_framework_simplejwt',
        'djoser',
    ]
    
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework_simplejwt.authentication.JWTAuthentication',
        ),
    }
    
    SIMPLE_JWT = {
        "AUTH_HEADER_TYPES": ("Bearer",),  #  "Bearer" 여야 React 쪽과 호환됨
    }
    
    
    DJOSER = {
        "LOGIN_FIELD": "username",  # 또는 "email"
    }
    
    

    ✅ 1-3. urls.py 설정

    from django.contrib import admin
    from django.urls import path, include
    
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('auth/', include('djoser.urls')),
        path('auth/', include('djoser.urls.jwt')),
    ]
    

    🔖 Djoser 의 공식문서에는 re_path 를 사용하고 있으나 re_path 대신 path 사용

    re_path는 정규표현식을 위한 것이므로 현재처럼 고정된 경로(auth/)에는 path()를 사용하는 것이 더 간결하고 명확합니다.

    ✔️ 위 urls.py 설정 Djoser가 제공하는 주요 JWT 엔드포인트 확인

    이 설정으로 다음과 같은 엔드포인트들이 활성화됩니다:

    Method URL 설명
    POST /auth/jwt/create/ 로그인 (access, refresh 토큰 발급)
    POST /auth/jwt/refresh/ access 토큰 갱신
    POST /auth/jwt/verify/ 토큰 유효성 확인
    GET /auth/users/me/ 현재 로그인한 사용자 정보 조회

    1-4. Django User 생성

    
    python manage.py createsuperuser
    
    

    1.5 api.http 테스트 실행

    api.http 파일

    ### JWT 로그인
    POST http://localhost:8000/auth/jwt/create/  HTTP/1.1
    Content-Type: application/json
    {
    
       "username": "admin",
       "password": "1111"
    }
    
      
    ### 토큰 변수 설정
    
    @refreshToken = 발급받은 갱신토큰값
      
    
    @accessToken = 발급받은 접근토큰값
    
      
    
    #######################  JWT
    
    GET http://localhost:8000/auth/users/me/ HTTP/1.1
    Content-Type: application/json
    Authorization: Bearer {{accessToken}}
    
      
      
    
    ### verify 유효성 체크
    
    POST http://localhost:8000/auth/jwt/verify/  HTTP/1.1
    Content-Type: application/json
    Authorization: Bearer {{accessToken}}
    
    {
       "token": "{{refreshToken}}"
    }
    
    ###  JWT   토큰 갱신
    POST http://localhost:8000/auth/jwt/refresh/  HTTP/1.1
    Content-Type: application/json
    
    {
       "refresh": "{{refreshToken}}"
    }
    

    2️⃣ 프론트엔드 설정 (React + Vite)

    1-1. Vite 기반 React 프로젝트 생성

    npm create vite@latest frontend -- --template react
    cd frontend
    npm install
    
    

    1-2. React Router 설치

    npm install react-router-dom
    
    

    1-3. 인증 Context 구현 (AuthContext.jsx)

    // src/context/AuthContext.tsx
    import React, { createContext, useState, useEffect } from "react";
    
    // Context 타입 정의
    interface AuthContextType {
      user: any;
      login: (username: string, password: string) => Promise<boolean>;
      logout: () => void;
    }
    
    interface AuthProviderProps {
      children: React.ReactNode;
    }
    
    // Context 생성
    export const AuthContext = createContext<AuthContextType | null>(null);
    
    // Provider 컴포넌트
    export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
      const [user, setUser] = useState(null);
    
      // 로그인 함수
      const login = async (username: string, password: string) => {
        try {
          const res = await fetch("http://localhost:8000/auth/jwt/create/", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({ username, password }),
          });
    
          if (!res.ok) throw new Error("Login failed");
    
          const data = await res.json();
          localStorage.setItem("token", data.access);
    
          // 유저 정보 가져오기
          const userRes = await fetch("http://localhost:8000/auth/users/me/", {
            headers: {
              "Content-Type": "application/json",
              Authorization: `Bearer ${data.access}`,
            },
          });
    
          if (!userRes.ok) throw new Error("User fetch failed");
    
          const userData = await userRes.json();
          setUser(userData);
          return true;
        } catch (err) {
          console.error("로그인 실패:", err);
          return false;
        }
      };
    
      // 로그아웃 함수
      const logout = () => {
        localStorage.removeItem("token");
        setUser(null);
      };
    
      // 초기화 함수 (새로고침 시 토큰이 있으면 유저 상태 복원)
      const initialize = async () => {
        const token = localStorage.getItem("token");
        if (!token) return;
    
        try {
          const res = await fetch("http://localhost:8000/auth/users/me/", {
            headers: {
              "Content-Type": "application/json",
              Authorization: `Bearer ${token}`,
            },
          });
    
          if (!res.ok) throw new Error();
    
          const userData = await res.json();
          setUser(userData);
        } catch {
          localStorage.removeItem("token");
        }
      };
    
      useEffect(() => {
        initialize();
      }, []);
    
      return (
        <AuthContext.Provider value={{ user, login, logout }}>
          {children}
        </AuthContext.Provider>
      );
    };
    
    

    1.4 라우터 설정 (App.jsx)

    // src/App.tsx
    import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
    import { AuthProvider } from "./context/AuthContext";
    import { Login } from "./components/Login";
    import { Dashboard } from "./components/Dashboard";
    
    function App() {
      return (
        <AuthProvider>
          <BrowserRouter>
            <nav style={{ display: "flex", gap: "1rem", marginBottom: "1rem" }}>
              <Link to="/login">Login</Link>
              <Link to="/dashboard">Dashboard</Link>
            </nav>
    
            <Routes>
              <Route path="/login" element={<Login />} />
              <Route path="/dashboard" element={<Dashboard />} />
            </Routes>
          </BrowserRouter>
        </AuthProvider>
      );
    }
    
    export default App;
    
    

    1-5 로그인 페이지 (Login.jsx)

    // src/components/Login.tsx
    import React, { useState, useContext } from "react";
    import { useNavigate } from "react-router-dom";
    import { AuthContext } from "../context/AuthContext";
    
    export const Login: React.FC = () => {
      const authContext = useContext(AuthContext);
    
      if (!authContext) {
        throw new Error(
          "AuthContext가 정의되지 않았습니다. AuthProvider 내에서 Login을 사용하고 있는지 확인하세요."
        );
      }
    
      const { login } = authContext;
      const [username, setUsername] = useState("");
      const [password, setPassword] = useState("");
      const navigate = useNavigate();
    
      const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        const success = await login(username, password);
        if (success) navigate("/dashboard");
      };
    
      return (
        <form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: "0.5rem", width: "250px" }}>
          <h2>Login</h2>
          <input
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
            placeholder="Username"
          />
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Password"
          />
          <button type="submit">Login</button>
        </form>
      );
    };
    
    

    1-6 대시보드 페이지 (Dashboard.jsx)

    // src/components/Dashboard.tsx
    import React, { useContext } from "react";
    import { Navigate } from "react-router-dom";
    import { AuthContext } from "../context/AuthContext";
    
    export const Dashboard: React.FC = () => {
      const authContext = useContext(AuthContext);
    
      if (!authContext || !authContext.user) {
        return <Navigate to="/login" replace />;
      }
    
      const { user, logout } = authContext;
    
      return (
        <div style={{ padding: "1rem" }}>
          <h2>Welcome, {user.username}</h2>
          <button onClick={logout}>Logout</button>
        </div>
      );
    };
    
    

    ✅ 최종 요약

    • JWT 로그인 및 토큰 저장(localStorage)

    • 로그인 상태 유지(useEffect + 초기 토큰 확인)

    • 보호된 페이지 접근(Dashboard에서 로그인 여부 확인)

    • React Router로 페이지 이동 처리

    TOP
    preload preload